/*****************************************************************************
 * Copyright (C) Stacy Curl. All rights reserved.                            *
 * ------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the BSD      *
 * style license a copy of which has been included with this distribution in *
 * the LICENSE.txt file.                                                     *
 *****************************************************************************/
package picounit.features.base;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

class DynamicProxyAggregateExtension<Extension>
	extends AbstractAggregateExtension<Extension> implements InvocationHandler {
	
	private final ProxyFactory proxyFactory = new ProxyFactory();

	public DynamicProxyAggregateExtension(Class<Extension> extensionType) {
		super(extensionType);
	}

	///////////////////////////////////////////////////////////////////////////////////////////////
	// Aggregate methods
	///////////////////////////////////////////////////////////////////////////////////////////////

	public Extension get() {
//		if (numberOfExtensions() == 0) {
//			throw new UnavailableExtention(extensionType());
//		}

		if (useSingleExtensionOptimisation() && numberOfExtensions() == 1) {
			return getOneExtension();
		}

		return proxyFactory.create(extensionType(), this);
	}

	private boolean useSingleExtensionOptimisation() {
		return true;
	}

	///////////////////////////////////////////////////////////////////////////////////////////////
	// InvocationHandler methods
	///////////////////////////////////////////////////////////////////////////////////////////////

	public Object invoke(Object proxy, Method method, Object[] input) throws Throwable {
		if (isListener(method)) {
			return invokeListener(method, input);
		}

		if (isModifier(method)) {
			return invokeModifier(method, input);
		}

		throw new UnsupportedOperationException(method.getName() +
			" is neither a listener, nor a modifier method");
	}
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	// Implementation
	///////////////////////////////////////////////////////////////////////////////////////////////

	private boolean isListener(Method method) {
		return method.getReturnType().equals(void.class);
	}

	private boolean isModifier(Method method) {
		Class<?> returnType = method.getReturnType();

		return !returnType.equals(void.class) && contains(method.getParameterTypes(), returnType);
	}

	private Object invokeListener(Method method, Object[] input)
		throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {

		for(Extension extension : extensions()) {
			method.invoke(extension, input);
		}

		return null;
	}

	private Object invokeModifier(Method method, Object[] input)
		throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		
		int returnTypeIndex = find(method.getParameterTypes(), method.getReturnType());

		Object[] output = shallowCopy(input);

		Object result = input[returnTypeIndex];

		for(Extension extension : extensions()) {
			result = method.invoke(extension, output);

			output[returnTypeIndex] = result;
		}

		return result;
	}

	private Object[] shallowCopy(Object[] array) {
		Object[] copy = new Object[array.length];

		for (int index = 0; index < array.length; index++ ) {
			copy[index] = array[index];
		}

		return copy;
	}

	private <T> boolean contains(T[] array, T searchFor) {
		return find(array, searchFor) != -1;
	}

	private <T> int find(T[] array, T searchFor) {
		for (int index = 0; index < array.length; index++ ) {
			if (searchFor.equals(array[index])) {
				return index;
			}
		}
		
		return -1;
	}
}
